package com.neuronbit.businessflow.intellij.psi;

import com.neuronbit.businessflow.intellij.BpmFileType;
import com.intellij.openapi.application.QueryExecutorBase;
import com.intellij.psi.*;
import com.intellij.psi.impl.beanProperties.BeanPropertyElement;
import com.intellij.psi.impl.light.LightVariableBuilder;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.RequestResultProcessor;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.search.UsageSearchContext;
import com.intellij.psi.search.searches.MethodReferencesSearch;
import com.intellij.psi.util.PropertyUtilBase;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.util.Processor;
import org.jetbrains.annotations.NotNull;

import java.util.List;

/**
 * The type Bpm method references search.
 */
public class BpmMethodReferencesSearch extends QueryExecutorBase<PsiReference, MethodReferencesSearch.SearchParameters> {
    @Override
    public void processQuery(@NotNull MethodReferencesSearch.SearchParameters queryParameters, @NotNull Processor<? super PsiReference> processor) {
        final PsiMethod method = queryParameters.getMethod();
        final PsiFile containingFile = method.getContainingFile();
        final PsiClass psiClass = method.getContainingClass();
        if (containingFile == null || psiClass == null) return;

        SearchScope scope = queryParameters.getEffectiveSearchScope();
        if (scope instanceof GlobalSearchScope) {
            scope = GlobalSearchScope.getScopeRestrictedByFileTypes((GlobalSearchScope)scope, BpmFileType.INSTANCE);
        }
        final String methodName = PropertyUtilBase.getPropertyNameByGetter(method);
        queryParameters.getOptimizer().searchWord(methodName, scope, UsageSearchContext.ANY, true, method, new MethodRequestResultProcessor(method));
//        queryParameters.getOptimizer().searchWord(methodName, scope, UsageSearchContext.ANY, true, method);
    }

    private static class MethodRequestResultProcessor extends RequestResultProcessor {
        private final PsiMethod myMethod;

        /**
         * Instantiates a new Method request result processor.
         *
         * @param method the method
         */
        MethodRequestResultProcessor(PsiMethod method) {
            myMethod = method;
        }

        @Override
        public boolean processTextOccurrence(@NotNull PsiElement element, int offsetInElement, @NotNull Processor<? super PsiReference> consumer) {
            List<PsiReference> references = PsiReferenceService.getService().getReferences(element, new PsiReferenceService.Hints(element, offsetInElement));
            for (PsiReference ref : references) {
                if (ReferenceRange.containsOffsetInElement(ref, offsetInElement) && !processReference(consumer, ref, myMethod)) {
                    return false;
                }
            }
            return true;
        }

        private static boolean processReference(Processor<? super PsiReference> consumer, PsiReference ref, PsiMethod method) {
            if (!method.isValid()) return true;

            if (ref instanceof ResolvingHint && !((ResolvingHint)ref).canResolveTo(PsiMethod.class)) {
                return true;
            }
            if (ref.isReferenceTo(method)) {
                return consumer.process(ref);
            }
            PsiElement refElement = ref.resolve();
            if (refElement instanceof LightVariableBuilder && method.equals(refElement.getNavigationElement())) {  // mvel declaration "then f3.value ... "
                return consumer.process(ref);
            }
            if (refElement instanceof BeanPropertyElement && method.equals(((BeanPropertyElement)refElement).getMethod())) {
                return consumer.process(ref);
            }

            return true;
        }
    }
}
